/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- *//* vim:set ts=4 sw=4 sts=4 et cin: *//* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */// HttpLog.h should generally be included first#include"HttpLog.h"#include"base/basictypes.h"#include"nsHttpHandler.h"#include"nsHttpTransaction.h"#include"nsHttpRequestHead.h"#include"nsHttpResponseHead.h"#include"nsHttpChunkedDecoder.h"#include"nsTransportUtils.h"#include"nsNetCID.h"#include"nsNetUtil.h"#include"nsIChannel.h"#include"nsIPipe.h"#include"nsCRT.h"#include"mozilla/Tokenizer.h"#include"TCPFastOpenLayer.h"#include"nsISeekableStream.h"#include"nsMultiplexInputStream.h"#include"nsStringStream.h"#include"nsComponentManagerUtils.h" // do_CreateInstance#include"nsIHttpActivityObserver.h"#include"nsSocketTransportService2.h"#include"nsICancelable.h"#include"nsIClassOfService.h"#include"nsIEventTarget.h"#include"nsIHttpChannelInternal.h"#include"nsIInputStream.h"#include"nsIThrottledInputChannel.h"#include"nsITransport.h"#include"nsIOService.h"#include"nsIRequestContext.h"#include"nsIHttpAuthenticator.h"#include"NSSErrorsService.h"#include"sslerr.h"#include<algorithm>//-----------------------------------------------------------------------------staticNS_DEFINE_CID(kMultiplexInputStream,NS_MULTIPLEXINPUTSTREAM_CID);// Place a limit on how much non-compliant HTTP can be skipped while// looking for a response header#define MAX_INVALID_RESPONSE_BODY_SIZE (1024 * 128)usingnamespacemozilla::net;namespacemozilla{namespacenet{//-----------------------------------------------------------------------------// helpers//-----------------------------------------------------------------------------staticvoidLogHeaders(constchar*lineStart){nsAutoCStringbuf;char*endOfLine;while((endOfLine=PL_strstr(lineStart,"\r\n"))){buf.Assign(lineStart,endOfLine-lineStart);if(PL_strcasestr(buf.get(),"authorization: ")||PL_strcasestr(buf.get(),"proxy-authorization: ")){char*p=PL_strchr(PL_strchr(buf.get(),' ')+1,' ');while(p&&*++p)*p='*';}LOG3((" %s\n",buf.get()));lineStart=endOfLine+2;}}//-----------------------------------------------------------------------------// nsHttpTransaction <public>//-----------------------------------------------------------------------------nsHttpTransaction::nsHttpTransaction():mLock("transaction lock"),mRequestSize(0),mRequestHead(nullptr),mResponseHead(nullptr),mReader(nullptr),mWriter(nullptr),mContentLength(-1),mContentRead(0),mTransferSize(0),mInvalidResponseBytesRead(0),mPushedStream(nullptr),mInitialRwin(0),mChunkedDecoder(nullptr),mStatus(NS_OK),mPriority(0),mRestartCount(0),mCaps(0),mHttpVersion(NS_HTTP_VERSION_UNKNOWN),mHttpResponseCode(0),mCurrentHttpResponseHeaderSize(0),mCapsToClear(0),mResponseIsComplete(false),mReadingStopped(false),mClosed(false),mConnected(false),mActivated(false),mActivatedAsH2(false),mHaveStatusLine(false),mHaveAllHeaders(false),mTransactionDone(false),mDidContentStart(false),mNoContent(false),mSentData(false),mReceivedData(false),mStatusEventPending(false),mHasRequestBody(false),mProxyConnectFailed(false),mHttpResponseMatched(false),mPreserveStream(false),mDispatchedAsBlocking(false),mResponseTimeoutEnabled(true),mForceRestart(false),mReuseOnRestart(false),mContentDecoding(false),mContentDecodingCheck(false),mDeferredSendProgress(false),mWaitingOnPipeOut(false),mReportedStart(false),mReportedResponseHeader(false),mForTakeResponseHead(nullptr),mResponseHeadTaken(false),mTopLevelOuterContentWindowId(0),mSubmittedRatePacing(false),mPassedRatePacing(false),mSynchronousRatePaceRequest(false),mClassOfService(0),m0RTTInProgress(false),mEarlyDataDisposition(EARLY_NONE),mFastOpenStatus(TFO_NOT_TRIED){LOG(("Creating nsHttpTransaction @%p\n",this));#ifdef MOZ_VALGRINDmemset(&mSelfAddr,0,sizeof(NetAddr));memset(&mPeerAddr,0,sizeof(NetAddr));#endifmSelfAddr.raw.family=PR_AF_UNSPEC;mPeerAddr.raw.family=PR_AF_UNSPEC;}voidnsHttpTransaction::ResumeReading(){MOZ_ASSERT(OnSocketThread(),"not on socket thread");if(!mReadingStopped){return;}LOG(("nsHttpTransaction::ResumeReading %p",this));mReadingStopped=false;if(mConnection){nsresultrv=mConnection->ResumeRecv();if(NS_FAILED(rv)){LOG((" resume failed with rv=%"PRIx32,static_cast<uint32_t>(rv)));}}}voidnsHttpTransaction::SetClassOfService(uint32_tcos){boolwasThrottling=mClassOfService&nsIClassOfService::Throttleable;mClassOfService=cos;boolisThrottling=mClassOfService&nsIClassOfService::Throttleable;if(mConnection&&wasThrottling!=isThrottling){// Do nothing until we are actually activated. For now// only remember the throttle flag. Call to MoveActiveTransaction// would add this transaction to the list too early.gHttpHandler->ConnMgr()->MoveActiveTransaction(this,isThrottling);if(mReadingStopped&&!isThrottling){ResumeReading();}}}nsHttpTransaction::~nsHttpTransaction(){LOG(("Destroying nsHttpTransaction @%p\n",this));if(mTransactionObserver){mTransactionObserver->Complete(this,NS_OK);}if(mPushedStream){mPushedStream->OnPushFailed();mPushedStream=nullptr;}if(mTokenBucketCancel){mTokenBucketCancel->Cancel(NS_ERROR_ABORT);mTokenBucketCancel=nullptr;}// Force the callbacks and connection to be released right nowmCallbacks=nullptr;mConnection=nullptr;deletemResponseHead;deletemForTakeResponseHead;deletemChunkedDecoder;ReleaseBlockingTransaction();}nsresultnsHttpTransaction::Init(uint32_tcaps,nsHttpConnectionInfo*cinfo,nsHttpRequestHead*requestHead,nsIInputStream*requestBody,uint64_trequestContentLength,boolrequestBodyHasHeaders,nsIEventTarget*target,nsIInterfaceRequestor*callbacks,nsITransportEventSink*eventsink,uint64_ttopLevelOuterContentWindowId,nsIAsyncInputStream**responseBody){nsresultrv;LOG(("nsHttpTransaction::Init [this=%p caps=%x]\n",this,caps));MOZ_ASSERT(cinfo);MOZ_ASSERT(requestHead);MOZ_ASSERT(target);MOZ_ASSERT(NS_IsMainThread());mTopLevelOuterContentWindowId=topLevelOuterContentWindowId;mActivityDistributor=services::GetActivityDistributor();if(!mActivityDistributor){returnNS_ERROR_NOT_AVAILABLE;}boolactivityDistributorActive;rv=mActivityDistributor->GetIsActive(&activityDistributorActive);if(NS_SUCCEEDED(rv)&&activityDistributorActive){// there are some observers registered at activity distributor, gather// nsISupports for the channel that called Init()LOG(("nsHttpTransaction::Init() " \"mActivityDistributor is active " \"this=%p",this));}else{// there is no observer, so don't use itactivityDistributorActive=false;mActivityDistributor=nullptr;}mChannel=do_QueryInterface(eventsink);nsCOMPtr<nsIHttpChannelInternal>httpChannelInternal=do_QueryInterface(eventsink);if(httpChannelInternal){rv=httpChannelInternal->GetResponseTimeoutEnabled(&mResponseTimeoutEnabled);if(NS_WARN_IF(NS_FAILED(rv))){returnrv;}rv=httpChannelInternal->GetInitialRwin(&mInitialRwin);MOZ_ASSERT(NS_SUCCEEDED(rv));}// create transport event sink proxy. it coalesces consecutive// events of the same status type.rv=net_NewTransportEventSinkProxy(getter_AddRefs(mTransportSink),eventsink,target);if(NS_FAILED(rv))returnrv;mConnInfo=cinfo;mCallbacks=callbacks;mConsumerTarget=target;mCaps=caps;if(requestHead->IsHead()){mNoContent=true;}// Make sure that there is "Content-Length: 0" header in the requestHead// in case of POST and PUT methods when there is no requestBody and// requestHead doesn't contain "Transfer-Encoding" header.//// RFC1945 section 7.2.2:// HTTP/1.0 requests containing an entity body must include a valid// Content-Length header field.//// RFC2616 section 4.4:// For compatibility with HTTP/1.0 applications, HTTP/1.1 requests// containing a message-body MUST include a valid Content-Length header// field unless the server is known to be HTTP/1.1 compliant.if((requestHead->IsPost()||requestHead->IsPut())&&!requestBody&&!requestHead->HasHeader(nsHttp::Transfer_Encoding)){rv=requestHead->SetHeader(nsHttp::Content_Length,NS_LITERAL_CSTRING("0"));MOZ_ASSERT(NS_SUCCEEDED(rv));}// grab a weak reference to the request headmRequestHead=requestHead;// make sure we eliminate any proxy specific headers from// the request if we are using CONNECTboolpruneProxyHeaders=cinfo->UsingConnect();mReqHeaderBuf.Truncate();requestHead->Flatten(mReqHeaderBuf,pruneProxyHeaders);if(LOG3_ENABLED()){LOG3(("http request [\n"));LogHeaders(mReqHeaderBuf.get());LOG3(("]\n"));}// If the request body does not include headers or if there is no request// body, then we must add the header/body separator manually.if(!requestBodyHasHeaders||!requestBody)mReqHeaderBuf.AppendLiteral("\r\n");// report the request headerif(mActivityDistributor){rv=mActivityDistributor->ObserveActivity(mChannel,NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_HEADER,PR_Now(),0,mReqHeaderBuf);if(NS_FAILED(rv)){LOG3(("ObserveActivity failed (%08x)",static_cast<uint32_t>(rv)));}}// Create a string stream for the request header buf (the stream holds// a non-owning reference to the request header data, so we MUST keep// mReqHeaderBuf around).nsCOMPtr<nsIInputStream>headers;rv=NS_NewByteInputStream(getter_AddRefs(headers),mReqHeaderBuf.get(),mReqHeaderBuf.Length());if(NS_FAILED(rv))returnrv;mHasRequestBody=!!requestBody;if(mHasRequestBody&&!requestContentLength){mHasRequestBody=false;}requestContentLength+=mReqHeaderBuf.Length();if(mHasRequestBody){// wrap the headers and request body in a multiplexed input stream.nsCOMPtr<nsIMultiplexInputStream>multi=do_CreateInstance(kMultiplexInputStream,&rv);if(NS_FAILED(rv))returnrv;rv=multi->AppendStream(headers);if(NS_FAILED(rv))returnrv;rv=multi->AppendStream(requestBody);if(NS_FAILED(rv))returnrv;// wrap the multiplexed input stream with a buffered input stream, so// that we write data in the largest chunks possible. this is actually// necessary to workaround some common server bugs (see bug 137155).rv=NS_NewBufferedInputStream(getter_AddRefs(mRequestStream),multi,nsIOService::gDefaultSegmentSize);if(NS_FAILED(rv))returnrv;}else{mRequestStream=headers;}nsCOMPtr<nsIThrottledInputChannel>throttled=do_QueryInterface(mChannel);nsIInputChannelThrottleQueue*queue;if(throttled){rv=throttled->GetThrottleQueue(&queue);// In case of failure, just carry on without throttling.if(NS_SUCCEEDED(rv)&&queue){nsCOMPtr<nsIAsyncInputStream>wrappedStream;rv=queue->WrapStream(mRequestStream,getter_AddRefs(wrappedStream));// Failure to throttle isn't sufficient reason to fail// initializationif(NS_SUCCEEDED(rv)){MOZ_ASSERT(wrappedStream!=nullptr);LOG(("nsHttpTransaction::Init %p wrapping input stream using throttle queue %p\n",this,queue));mRequestStream=do_QueryInterface(wrappedStream);}}}// make sure request content-length fits within js MAX_SAFE_INTEGERmRequestSize=InScriptableRange(requestContentLength)?static_cast<int64_t>(requestContentLength):-1;// create pipe for response streamrv=NS_NewPipe2(getter_AddRefs(mPipeIn),getter_AddRefs(mPipeOut),true,true,nsIOService::gDefaultSegmentSize,nsIOService::gDefaultSegmentCount);if(NS_FAILED(rv))returnrv;#ifdef WIN32 // bug 1153929MOZ_DIAGNOSTIC_ASSERT(mPipeOut);uint32_t*vtable=(uint32_t*)mPipeOut.get();MOZ_DIAGNOSTIC_ASSERT(*vtable!=0);#endif // WIN32nsCOMPtr<nsIAsyncInputStream>tmp(mPipeIn);tmp.forget(responseBody);returnNS_OK;}// This method should only be used on the socket threadnsAHttpConnection*nsHttpTransaction::Connection(){MOZ_ASSERT(OnSocketThread(),"not on socket thread");returnmConnection.get();}already_AddRefed<nsAHttpConnection>nsHttpTransaction::GetConnectionReference(){MutexAutoLocklock(mLock);RefPtr<nsAHttpConnection>connection(mConnection);returnconnection.forget();}nsHttpResponseHead*nsHttpTransaction::TakeResponseHead(){MOZ_ASSERT(!mResponseHeadTaken,"TakeResponseHead called 2x");// Lock TakeResponseHead() against main threadMutexAutoLocklock(*nsHttp::GetLock());mResponseHeadTaken=true;// Prefer mForTakeResponseHead over mResponseHead. It is always a complete// set of headers.nsHttpResponseHead*head;if(mForTakeResponseHead){head=mForTakeResponseHead;mForTakeResponseHead=nullptr;returnhead;}// Even in OnStartRequest() the headers won't be available if we were// canceledif(!mHaveAllHeaders){NS_WARNING("response headers not available or incomplete");returnnullptr;}head=mResponseHead;mResponseHead=nullptr;returnhead;}voidnsHttpTransaction::SetProxyConnectFailed(){mProxyConnectFailed=true;}nsHttpRequestHead*nsHttpTransaction::RequestHead(){returnmRequestHead;}uint32_tnsHttpTransaction::Http1xTransactionCount(){return1;}nsresultnsHttpTransaction::TakeSubTransactions(nsTArray<RefPtr<nsAHttpTransaction>>&outTransactions){returnNS_ERROR_NOT_IMPLEMENTED;}//----------------------------------------------------------------------------// nsHttpTransaction::nsAHttpTransaction//----------------------------------------------------------------------------voidnsHttpTransaction::SetConnection(nsAHttpConnection*conn){{MutexAutoLocklock(mLock);mConnection=conn;}}voidnsHttpTransaction::OnActivated(boolh2){MOZ_ASSERT(OnSocketThread());mActivatedAsH2=h2;if(mActivated){return;}mActivated=true;gHttpHandler->ConnMgr()->AddActiveTransaction(this,mClassOfService&nsIClassOfService::Throttleable);}voidnsHttpTransaction::GetSecurityCallbacks(nsIInterfaceRequestor**cb){MutexAutoLocklock(mLock);nsCOMPtr<nsIInterfaceRequestor>tmp(mCallbacks);tmp.forget(cb);}voidnsHttpTransaction::SetSecurityCallbacks(nsIInterfaceRequestor*aCallbacks){{MutexAutoLocklock(mLock);mCallbacks=aCallbacks;}if(gSocketTransportService){RefPtr<UpdateSecurityCallbacks>event=newUpdateSecurityCallbacks(this,aCallbacks);gSocketTransportService->Dispatch(event,nsIEventTarget::DISPATCH_NORMAL);}}voidnsHttpTransaction::OnTransportStatus(nsITransport*transport,nsresultstatus,int64_tprogress){LOG(("nsHttpTransaction::OnSocketStatus [this=%p status=%"PRIx32" progress=%"PRId64"]\n",this,static_cast<uint32_t>(status),progress));if(status==NS_NET_STATUS_CONNECTED_TO||status==NS_NET_STATUS_WAITING_FOR){nsISocketTransport*socketTransport=mConnection?mConnection->Transport():nullptr;if(socketTransport){MutexAutoLocklock(mLock);socketTransport->GetSelfAddr(&mSelfAddr);socketTransport->GetPeerAddr(&mPeerAddr);}}// If the timing is enabled, and we are not using a persistent connection// then the requestStart timestamp will be null, so we mark the timestamps// for domainLookupStart/End and connectStart/End// If we are using a persistent connection they will remain null,// and the correct value will be returned in Performance.if(TimingEnabled()&&GetRequestStart().IsNull()){if(status==NS_NET_STATUS_RESOLVING_HOST){SetDomainLookupStart(TimeStamp::Now(),true);}elseif(status==NS_NET_STATUS_RESOLVED_HOST){SetDomainLookupEnd(TimeStamp::Now());}elseif(status==NS_NET_STATUS_CONNECTING_TO){SetConnectStart(TimeStamp::Now());}elseif(status==NS_NET_STATUS_CONNECTED_TO){SetConnectEnd(TimeStamp::Now(),true);}elseif(status==NS_NET_STATUS_TLS_HANDSHAKE_ENDED){SetConnectEnd(TimeStamp::Now(),false);}elseif(status==NS_NET_STATUS_SENDING_TO){// Set the timestamp to Now(), only if it nullSetRequestStart(TimeStamp::Now(),true);}}if(!mTransportSink)return;MOZ_ASSERT(OnSocketThread(),"not on socket thread");// Need to do this before the STATUS_RECEIVING_FROM check below, to make// sure that the activity distributor gets told about all status events.if(mActivityDistributor){// upon STATUS_WAITING_FOR; report request body sentif((mHasRequestBody)&&(status==NS_NET_STATUS_WAITING_FOR)){nsresultrv=mActivityDistributor->ObserveActivity(mChannel,NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_BODY_SENT,PR_Now(),0,EmptyCString());if(NS_FAILED(rv)){LOG3(("ObserveActivity failed (%08x)",static_cast<uint32_t>(rv)));}}// report the status and progressnsresultrv=mActivityDistributor->ObserveActivity(mChannel,NS_HTTP_ACTIVITY_TYPE_SOCKET_TRANSPORT,static_cast<uint32_t>(status),PR_Now(),progress,EmptyCString());if(NS_FAILED(rv)){LOG3(("ObserveActivity failed (%08x)",static_cast<uint32_t>(rv)));}}// nsHttpChannel synthesizes progress events in OnDataAvailableif(status==NS_NET_STATUS_RECEIVING_FROM)return;int64_tprogressMax;if(status==NS_NET_STATUS_SENDING_TO){// suppress progress when only writing request headersif(!mHasRequestBody){LOG(("nsHttpTransaction::OnTransportStatus %p ""SENDING_TO without request body\n",this));return;}if(mReader){// A mRequestStream method is on the stack - wait.LOG(("nsHttpTransaction::OnSocketStatus [this=%p] ""Skipping Re-Entrant NS_NET_STATUS_SENDING_TO\n",this));// its ok to coalesce several of these into one deferred eventmDeferredSendProgress=true;return;}nsCOMPtr<nsISeekableStream>seekable=do_QueryInterface(mRequestStream);if(!seekable){LOG(("nsHttpTransaction::OnTransportStatus %p ""SENDING_TO without seekable request stream\n",this));progress=0;}else{int64_tprog=0;seekable->Tell(&prog);progress=prog;}// when uploading, we include the request headers in the progress// notifications.progressMax=mRequestSize;}else{progress=0;progressMax=0;}mTransportSink->OnTransportStatus(transport,status,progress,progressMax);}boolnsHttpTransaction::IsDone(){returnmTransactionDone;}nsresultnsHttpTransaction::Status(){returnmStatus;}uint32_tnsHttpTransaction::Caps(){returnmCaps&~mCapsToClear;}voidnsHttpTransaction::SetDNSWasRefreshed(){MOZ_ASSERT(NS_IsMainThread(),"SetDNSWasRefreshed on main thread only!");mCapsToClear|=NS_HTTP_REFRESH_DNS;}nsresultnsHttpTransaction::ReadRequestSegment(nsIInputStream*stream,void*closure,constchar*buf,uint32_toffset,uint32_tcount,uint32_t*countRead){// For the tracking of sent bytes that we used to do for the networkstats// API, please see bug 1318883 where it was removed.nsHttpTransaction*trans=(nsHttpTransaction*)closure;nsresultrv=trans->mReader->OnReadSegment(buf,count,countRead);if(NS_FAILED(rv))returnrv;trans->mSentData=true;returnNS_OK;}nsresultnsHttpTransaction::ReadSegments(nsAHttpSegmentReader*reader,uint32_tcount,uint32_t*countRead){MOZ_ASSERT(OnSocketThread(),"not on socket thread");if(mTransactionDone){*countRead=0;returnmStatus;}if(!mConnected&&!m0RTTInProgress){mConnected=true;mConnection->GetSecurityInfo(getter_AddRefs(mSecurityInfo));}mDeferredSendProgress=false;mReader=reader;nsresultrv=mRequestStream->ReadSegments(ReadRequestSegment,this,count,countRead);mReader=nullptr;if(m0RTTInProgress&&(mEarlyDataDisposition==EARLY_NONE)&&NS_SUCCEEDED(rv)&&(*countRead>0)){mEarlyDataDisposition=EARLY_SENT;}if(mDeferredSendProgress&&mConnection&&mConnection->Transport()){// to avoid using mRequestStream concurrently, OnTransportStatus()// did not report upload status off the ReadSegments() stack from nsSocketTransport// do it now.OnTransportStatus(mConnection->Transport(),NS_NET_STATUS_SENDING_TO,0);}mDeferredSendProgress=false;if(mForceRestart){// The forceRestart condition was dealt with on the stack, but it did not// clear the flag because nsPipe in the readsegment stack clears out// return codes, so we need to use the flag here as a cue to return ERETARGETEDif(NS_SUCCEEDED(rv)){rv=NS_BINDING_RETARGETED;}mForceRestart=false;}// if read would block then we need to AsyncWait on the request stream.// have callback occur on socket thread so we stay synchronized.if(rv==NS_BASE_STREAM_WOULD_BLOCK){nsCOMPtr<nsIAsyncInputStream>asyncIn=do_QueryInterface(mRequestStream);if(asyncIn){nsCOMPtr<nsIEventTarget>target;Unused<<gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target));if(target)asyncIn->AsyncWait(this,0,0,target);else{NS_ERROR("no socket thread event target");rv=NS_ERROR_UNEXPECTED;}}}returnrv;}nsresultnsHttpTransaction::WritePipeSegment(nsIOutputStream*stream,void*closure,char*buf,uint32_toffset,uint32_tcount,uint32_t*countWritten){nsHttpTransaction*trans=(nsHttpTransaction*)closure;if(trans->mTransactionDone)returnNS_BASE_STREAM_CLOSED;// stop iteratingif(trans->TimingEnabled()){// Set the timestamp to Now(), only if it nulltrans->SetResponseStart(TimeStamp::Now(),true);}// Bug 1153929 - add checks to fix windows crashMOZ_ASSERT(trans->mWriter);if(!trans->mWriter){returnNS_ERROR_UNEXPECTED;}nsresultrv;//// OK, now let the caller fill this segment with data.//rv=trans->mWriter->OnWriteSegment(buf,count,countWritten);if(NS_FAILED(rv))returnrv;// caller didn't want to write anythingMOZ_ASSERT(*countWritten>0,"bad writer");trans->mReceivedData=true;trans->mTransferSize+=*countWritten;// Let the transaction "play" with the buffer. It is free to modify// the contents of the buffer and/or modify countWritten.// - Bytes in HTTP headers don't count towards countWritten, so the input// side of pipe (aka nsHttpChannel's mTransactionPump) won't hit// OnInputStreamReady until all headers have been parsed.//rv=trans->ProcessData(buf,*countWritten,countWritten);if(NS_FAILED(rv))trans->Close(rv);returnrv;// failure code only stops WriteSegments; it is not propagated.}boolnsHttpTransaction::ShouldStopReading(){if(mActivatedAsH2){// Throttling feature is now disabled for http/2 transactions// because of bug 1367861. The logic around mActivatedAsH2// will be removed when that is fixedreturnfalse;}if(!gHttpHandler->ConnMgr()->ShouldStopReading(this,mClassOfService&nsIClassOfService::Throttleable)){// We are not obligated to throttlereturnfalse;}if(mContentRead<16000){// Let the first bytes go, it may also well be all the content we getLOG(("nsHttpTransaction::ShouldStopReading too few content (%"PRIi64") this=%p",mContentRead,this));returnfalse;}if(gHttpHandler->ConnMgr()->IsConnEntryUnderPressure(mConnInfo)){LOG(("nsHttpTransaction::ShouldStopReading entry pressure this=%p",this));// This is expensive to check (two hashtable lookups) but may help// freeing connections for active tab transactions.returnfalse;}returntrue;}nsresultnsHttpTransaction::WriteSegments(nsAHttpSegmentWriter*writer,uint32_tcount,uint32_t*countWritten){LOG(("nsHttpTransaction::WriteSegments %p",this));MOZ_ASSERT(OnSocketThread(),"not on socket thread");if(mTransactionDone){returnNS_SUCCEEDED(mStatus)?NS_BASE_STREAM_CLOSED:mStatus;}if(ShouldStopReading()){// Must remember that we have to call ResumeRecv() on our connection when// called back by the conn manager to resume reading.LOG(("nsHttpTransaction::WriteSegments %p response throttled",this));mReadingStopped=true;// This makes the underlaying connection or stream wait for explicit resume.// For h1 this means we stop reading from the socket.// For h2 this means we stop updating recv window for the stream.returnNS_BASE_STREAM_WOULD_BLOCK;}mWriter=writer;#ifdef WIN32 // bug 1153929MOZ_DIAGNOSTIC_ASSERT(mPipeOut);uint32_t*vtable=(uint32_t*)mPipeOut.get();MOZ_DIAGNOSTIC_ASSERT(*vtable!=0);#endif // WIN32if(!mPipeOut){returnNS_ERROR_UNEXPECTED;}nsresultrv=mPipeOut->WriteSegments(WritePipeSegment,this,count,countWritten);mWriter=nullptr;if(mForceRestart){// The forceRestart condition was dealt with on the stack, but it did not// clear the flag because nsPipe in the writesegment stack clears out// return codes, so we need to use the flag here as a cue to return ERETARGETEDif(NS_SUCCEEDED(rv)){rv=NS_BINDING_RETARGETED;}mForceRestart=false;}// if pipe would block then we need to AsyncWait on it. have callback// occur on socket thread so we stay synchronized.if(rv==NS_BASE_STREAM_WOULD_BLOCK){nsCOMPtr<nsIEventTarget>target;Unused<<gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target));if(target){mPipeOut->AsyncWait(this,0,0,target);mWaitingOnPipeOut=true;}else{NS_ERROR("no socket thread event target");rv=NS_ERROR_UNEXPECTED;}}returnrv;}voidnsHttpTransaction::Close(nsresultreason){LOG(("nsHttpTransaction::Close [this=%p reason=%"PRIx32"]\n",this,static_cast<uint32_t>(reason)));if(!mClosed){gHttpHandler->ConnMgr()->RemoveActiveTransaction(this,mClassOfService&nsIClassOfService::Throttleable);mActivated=false;}MOZ_ASSERT(OnSocketThread(),"not on socket thread");if(reason==NS_BINDING_RETARGETED){LOG((" close %p skipped due to ERETARGETED\n",this));return;}if(mClosed){LOG((" already closed\n"));return;}if(mTransactionObserver){mTransactionObserver->Complete(this,reason);mTransactionObserver=nullptr;}if(mTokenBucketCancel){mTokenBucketCancel->Cancel(reason);mTokenBucketCancel=nullptr;}if(mActivityDistributor){// report the reponse is complete if not already reportedif(!mResponseIsComplete){nsresultrv=mActivityDistributor->ObserveActivity(mChannel,NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE,PR_Now(),static_cast<uint64_t>(mContentRead),EmptyCString());if(NS_FAILED(rv)){LOG3(("ObserveActivity failed (%08x)",static_cast<uint32_t>(rv)));}}// report that this transaction is closingnsresultrv=mActivityDistributor->ObserveActivity(mChannel,NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,NS_HTTP_ACTIVITY_SUBTYPE_TRANSACTION_CLOSE,PR_Now(),0,EmptyCString());if(NS_FAILED(rv)){LOG3(("ObserveActivity failed (%08x)",static_cast<uint32_t>(rv)));}}// we must no longer reference the connection! find out if the// connection was being reused before letting it go.boolconnReused=false;if(mConnection){connReused=mConnection->IsReused();}mConnected=false;mTunnelProvider=nullptr;//// if the connection was reset or closed before we wrote any part of the// request or if we wrote the request but didn't receive any part of the// response and the connection was being reused, then we can (and really// should) assume that we wrote to a stale connection and we must therefore// repeat the request over a new connection.//// We have decided to retry not only in case of the reused connections, but// all safe methods(bug 1236277).//// NOTE: the conditions under which we will automatically retry the HTTP// request have to be carefully selected to avoid duplication of the// request from the point-of-view of the server. such duplication could// have dire consequences including repeated purchases, etc.//// NOTE: because of the way SSL proxy CONNECT is implemented, it is// possible that the transaction may have received data without having// sent any data. for this reason, mSendData == FALSE does not imply// mReceivedData == FALSE. (see bug 203057 for more info.)//// Never restart transactions that are marked as sticky to their conenction.// We use that capability to identify transactions bound to connection based// authentication. Reissuing them on a different connections will break// this bondage. Major issue may arise when there is an NTLM message auth// header on the transaction and we send it to a different NTLM authenticated// connection. It will break that connection and also confuse the channel's// auth provider, beliving the cached credentials are wrong and asking for// the password mistakenly again from the user.if((reason==NS_ERROR_NET_RESET||reason==NS_OK||reason==psm::GetXPCOMFromNSSError(SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA))&&(!(mCaps&NS_HTTP_STICKY_CONNECTION)||(mCaps&NS_HTTP_CONNECTION_RESTARTABLE))){if(mForceRestart&&NS_SUCCEEDED(Restart())){if(mResponseHead){mResponseHead->Reset();}mContentRead=0;mContentLength=-1;deletemChunkedDecoder;mChunkedDecoder=nullptr;mHaveStatusLine=false;mHaveAllHeaders=false;mHttpResponseMatched=false;mResponseIsComplete=false;mDidContentStart=false;mNoContent=false;mSentData=false;mReceivedData=false;LOG(("transaction force restarted\n"));return;}// reallySentData is meant to separate the instances where data has// been sent by this transaction but buffered at a higher level while// a TLS session (perhaps via a tunnel) is setup.boolreallySentData=mSentData&&(!mConnection||mConnection->BytesWritten());if(reason==psm::GetXPCOMFromNSSError(SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA)||(!mReceivedData&&((mRequestHead&&mRequestHead->IsSafeMethod())||!reallySentData||connReused))){// if restarting fails, then we must proceed to close the pipe,// which will notify the channel that the transaction failed.if(NS_SUCCEEDED(Restart()))return;}}if((mChunkedDecoder||(mContentLength>=int64_t(0)))&&(NS_SUCCEEDED(reason)&&!mResponseIsComplete)){NS_WARNING("Partial transfer, incomplete HTTP response received");if((mHttpResponseCode/100==2)&&(mHttpVersion>=NS_HTTP_VERSION_1_1)){FrameCheckLevelclevel=gHttpHandler->GetEnforceH1Framing();if(clevel>=FRAMECHECK_BARELY){if((clevel==FRAMECHECK_STRICT)||(mChunkedDecoder&&mChunkedDecoder->GetChunkRemaining())||(!mChunkedDecoder&&!mContentDecoding&&mContentDecodingCheck)){reason=NS_ERROR_NET_PARTIAL_TRANSFER;LOG(("Partial transfer, incomplete HTTP response received: %s",mChunkedDecoder?"broken chunk":"c-l underrun"));}}}if(mConnection){// whether or not we generate an error for the transaction// bad framing means we don't want a pconnmConnection->DontReuse();}}boolrelConn=true;if(NS_SUCCEEDED(reason)){// the server has not sent the final \r\n terminating the header// section, and there may still be a header line unparsed. let's make// sure we parse the remaining header line, and then hopefully, the// response will be usable (see bug 88792).if(!mHaveAllHeaders){chardata='\n';uint32_tunused;Unused<<ParseHead(&data,1,&unused);if(mResponseHead->Version()==NS_HTTP_VERSION_0_9){// Reject 0 byte HTTP/0.9 Responses - bug 423506LOG(("nsHttpTransaction::Close %p 0 Byte 0.9 Response",this));reason=NS_ERROR_NET_RESET;}}// honor the sticky connection flag...if(mCaps&NS_HTTP_STICKY_CONNECTION)relConn=false;}// mTimings.responseEnd is normally recorded based on the end of a// HTTP delimiter such as chunked-encodings or content-length. However,// EOF or an error still require an end time be recorded.if(TimingEnabled()){constTimingStructtimings=Timings();if(timings.responseEnd.IsNull()&&!timings.responseStart.IsNull()){SetResponseEnd(TimeStamp::Now());}}if(relConn&&mConnection){MutexAutoLocklock(mLock);mConnection=nullptr;}mStatus=reason;mTransactionDone=true;// forcibly flag the transaction as completemClosed=true;ReleaseBlockingTransaction();// release some resources that we no longer needmRequestStream=nullptr;mReqHeaderBuf.Truncate();mLineBuf.Truncate();if(mChunkedDecoder){deletemChunkedDecoder;mChunkedDecoder=nullptr;}// closing this pipe triggers the channel's OnStopRequest method.mPipeOut->CloseWithStatus(reason);#ifdef WIN32 // bug 1153929MOZ_DIAGNOSTIC_ASSERT(mPipeOut);uint32_t*vtable=(uint32_t*)mPipeOut.get();MOZ_DIAGNOSTIC_ASSERT(*vtable!=0);mPipeOut=nullptr;// just in case#endif // WIN32}nsHttpConnectionInfo*nsHttpTransaction::ConnectionInfo(){returnmConnInfo.get();}bool// NOTE BASE CLASSnsAHttpTransaction::ResponseTimeoutEnabled()const{returnfalse;}PRIntervalTime// NOTE BASE CLASSnsAHttpTransaction::ResponseTimeout(){returngHttpHandler->ResponseTimeout();}boolnsHttpTransaction::ResponseTimeoutEnabled()const{returnmResponseTimeoutEnabled;}//-----------------------------------------------------------------------------// nsHttpTransaction <private>//-----------------------------------------------------------------------------nsresultnsHttpTransaction::Restart(){MOZ_ASSERT(OnSocketThread(),"not on socket thread");// limit the number of restart attempts - bug 92224if(++mRestartCount>=gHttpHandler->MaxRequestAttempts()){LOG(("reached max request attempts, failing transaction @%p\n",this));returnNS_ERROR_NET_RESET;}LOG(("restarting transaction @%p\n",this));mTunnelProvider=nullptr;// rewind streams in case we already wrote out the requestnsCOMPtr<nsISeekableStream>seekable=do_QueryInterface(mRequestStream);if(seekable)seekable->Seek(nsISeekableStream::NS_SEEK_SET,0);// clear old connection state...mSecurityInfo=nullptr;if(mConnection){if(!mReuseOnRestart){mConnection->DontReuse();}MutexAutoLocklock(mLock);mConnection=nullptr;}// Reset this to our default state, since this may change from one restart// to the nextmReuseOnRestart=false;if(!mConnInfo->GetRoutedHost().IsEmpty()){MutexAutoLocklock(*nsHttp::GetLock());RefPtr<nsHttpConnectionInfo>ci;mConnInfo->CloneAsDirectRoute(getter_AddRefs(ci));mConnInfo=ci;if(mRequestHead){DebugOnly<nsresult>rv=mRequestHead->SetHeader(nsHttp::Alternate_Service_Used,NS_LITERAL_CSTRING("0"));MOZ_ASSERT(NS_SUCCEEDED(rv));}}returngHttpHandler->InitiateTransaction(this,mPriority);}char*nsHttpTransaction::LocateHttpStart(char*buf,uint32_tlen,boolaAllowPartialMatch){MOZ_ASSERT(!aAllowPartialMatch||mLineBuf.IsEmpty());staticconstcharHTTPHeader[]="HTTP/1.";staticconstuint32_tHTTPHeaderLen=sizeof(HTTPHeader)-1;staticconstcharHTTP2Header[]="HTTP/2.0";staticconstuint32_tHTTP2HeaderLen=sizeof(HTTP2Header)-1;// ShoutCast ICY is treated as HTTP/1.0staticconstcharICYHeader[]="ICY ";staticconstuint32_tICYHeaderLen=sizeof(ICYHeader)-1;if(aAllowPartialMatch&&(len<HTTPHeaderLen))return(PL_strncasecmp(buf,HTTPHeader,len)==0)?buf:nullptr;// mLineBuf can contain partial match from previous searchif(!mLineBuf.IsEmpty()){MOZ_ASSERT(mLineBuf.Length()<HTTPHeaderLen);int32_tcheckChars=std::min(len,HTTPHeaderLen-mLineBuf.Length());if(PL_strncasecmp(buf,HTTPHeader+mLineBuf.Length(),checkChars)==0){mLineBuf.Append(buf,checkChars);if(mLineBuf.Length()==HTTPHeaderLen){// We've found whole HTTPHeader sequence. Return pointer at the// end of matched sequence since it is stored in mLineBuf.return(buf+checkChars);}// Response matches pattern but is still incomplete.return0;}// Previous partial match together with new data doesn't match the// pattern. Start the search again.mLineBuf.Truncate();}boolfirstByte=true;while(len>0){if(PL_strncasecmp(buf,HTTPHeader,std::min<uint32_t>(len,HTTPHeaderLen))==0){if(len<HTTPHeaderLen){// partial HTTPHeader sequence found// save partial match to mLineBufmLineBuf.Assign(buf,len);return0;}// whole HTTPHeader sequence foundreturnbuf;}// At least "SmarterTools/2.0.3974.16813" generates nonsensical// HTTP/2.0 responses to our HTTP/1 requests. Treat the minimal case of// it as HTTP/1.1 to be compatible with old versions of ourselves and// other browsersif(firstByte&&!mInvalidResponseBytesRead&&len>=HTTP2HeaderLen&&(PL_strncasecmp(buf,HTTP2Header,HTTP2HeaderLen)==0)){LOG(("nsHttpTransaction:: Identified HTTP/2.0 treating as 1.x\n"));returnbuf;}// Treat ICY (AOL/Nullsoft ShoutCast) non-standard header in same fashion// as HTTP/2.0 is treated above. This will allow "ICY " to be interpretted// as HTTP/1.0 in nsHttpResponseHead::ParseVersionif(firstByte&&!mInvalidResponseBytesRead&&len>=ICYHeaderLen&&(PL_strncasecmp(buf,ICYHeader,ICYHeaderLen)==0)){LOG(("nsHttpTransaction:: Identified ICY treating as HTTP/1.0\n"));returnbuf;}if(!nsCRT::IsAsciiSpace(*buf))firstByte=false;buf++;len--;}return0;}nsresultnsHttpTransaction::ParseLine(nsACString&line){LOG(("nsHttpTransaction::ParseLine [%s]\n",PromiseFlatCString(line).get()));nsresultrv=NS_OK;if(!mHaveStatusLine){mResponseHead->ParseStatusLine(line);mHaveStatusLine=true;// XXX this should probably never happenif(mResponseHead->Version()==NS_HTTP_VERSION_0_9)mHaveAllHeaders=true;}else{rv=mResponseHead->ParseHeaderLine(line);}returnrv;}nsresultnsHttpTransaction::ParseLineSegment(char*segment,uint32_tlen){NS_PRECONDITION(!mHaveAllHeaders,"already have all headers");if(!mLineBuf.IsEmpty()&&mLineBuf.Last()=='\n'){// trim off the new line char, and if this segment is// not a continuation of the previous or if we haven't// parsed the status line yet, then parse the contents// of mLineBuf.mLineBuf.Truncate(mLineBuf.Length()-1);if(!mHaveStatusLine||(*segment!=' '&&*segment!='\t')){nsresultrv=ParseLine(mLineBuf);mLineBuf.Truncate();if(NS_FAILED(rv)){returnrv;}}}// append segment to mLineBuf...mLineBuf.Append(segment,len);// a line buf with only a new line char signifies the end of headers.if(mLineBuf.First()=='\n'){mLineBuf.Truncate();// discard this response if it is a 100 continue or other 1xx status.uint16_tstatus=mResponseHead->Status();if((status!=101)&&(status/100==1)){LOG(("ignoring 1xx response\n"));mHaveStatusLine=false;mHttpResponseMatched=false;mConnection->SetLastTransactionExpectedNoContent(true);mResponseHead->Reset();returnNS_OK;}mHaveAllHeaders=true;}returnNS_OK;}nsresultnsHttpTransaction::ParseHead(char*buf,uint32_tcount,uint32_t*countRead){nsresultrv;uint32_tlen;char*eol;LOG(("nsHttpTransaction::ParseHead [count=%u]\n",count));*countRead=0;NS_PRECONDITION(!mHaveAllHeaders,"oops");// allocate the response head object if necessaryif(!mResponseHead){mResponseHead=newnsHttpResponseHead();if(!mResponseHead)returnNS_ERROR_OUT_OF_MEMORY;// report that we have a least some of the responseif(mActivityDistributor&&!mReportedStart){mReportedStart=true;rv=mActivityDistributor->ObserveActivity(mChannel,NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_START,PR_Now(),0,EmptyCString());if(NS_FAILED(rv)){LOG3(("ObserveActivity failed (%08x)",static_cast<uint32_t>(rv)));}}}if(!mHttpResponseMatched){// Normally we insist on seeing HTTP/1.x in the first few bytes,// but if we are on a persistent connection and the previous transaction// was not supposed to have any content then we need to be prepared// to skip over a response body that the server may have sent even// though it wasn't allowed.if(!mConnection||!mConnection->LastTransactionExpectedNoContent()){// tolerate only minor junk before the status linemHttpResponseMatched=true;char*p=LocateHttpStart(buf,std::min<uint32_t>(count,11),true);if(!p){// Treat any 0.9 style response of a put as a failure.if(mRequestHead->IsPut())returnNS_ERROR_ABORT;mResponseHead->ParseStatusLine(EmptyCString());mHaveStatusLine=true;mHaveAllHeaders=true;returnNS_OK;}if(p>buf){// skip over the junkmInvalidResponseBytesRead+=p-buf;*countRead=p-buf;buf=p;}}else{char*p=LocateHttpStart(buf,count,false);if(p){mInvalidResponseBytesRead+=p-buf;*countRead=p-buf;buf=p;mHttpResponseMatched=true;}else{mInvalidResponseBytesRead+=count;*countRead=count;if(mInvalidResponseBytesRead>MAX_INVALID_RESPONSE_BODY_SIZE){LOG(("nsHttpTransaction::ParseHead() ""Cannot find Response Header\n"));// cannot go back and call this 0.9 anymore as we// have thrown away a lot of the leading junkreturnNS_ERROR_ABORT;}returnNS_OK;}}}// otherwise we can assume that we don't have a HTTP/0.9 response.MOZ_ASSERT(mHttpResponseMatched);while((eol=static_cast<char*>(memchr(buf,'\n',count-*countRead)))!=nullptr){// found line in range [buf:eol]len=eol-buf+1;*countRead+=len;// actually, the line is in the range [buf:eol-1]if((eol>buf)&&(*(eol-1)=='\r'))len--;buf[len-1]='\n';rv=ParseLineSegment(buf,len);if(NS_FAILED(rv))returnrv;if(mHaveAllHeaders)returnNS_OK;// skip over linebuf=eol+1;if(!mHttpResponseMatched){// a 100 class response has caused us to throw away that set of// response headers and look for the next responsereturnNS_ERROR_NET_INTERRUPT;}}// do something about a partial header lineif(!mHaveAllHeaders&&(len=count-*countRead)){*countRead=count;// ignore a trailing carriage return, and don't bother calling// ParseLineSegment if buf only contains a carriage return.if((buf[len-1]=='\r')&&(--len==0))returnNS_OK;rv=ParseLineSegment(buf,len);if(NS_FAILED(rv))returnrv;}returnNS_OK;}nsresultnsHttpTransaction::HandleContentStart(){LOG(("nsHttpTransaction::HandleContentStart [this=%p]\n",this));MOZ_ASSERT(OnSocketThread(),"not on socket thread");if(mResponseHead){if(mEarlyDataDisposition==EARLY_ACCEPTED){Unused<<mResponseHead->SetHeader(nsHttp::X_Firefox_Early_Data,NS_LITERAL_CSTRING("accepted"));}elseif(mEarlyDataDisposition==EARLY_SENT){Unused<<mResponseHead->SetHeader(nsHttp::X_Firefox_Early_Data,NS_LITERAL_CSTRING("sent"));}// no header on NONE caseif(mFastOpenStatus==TFO_DATA_SENT){Unused<<mResponseHead->SetHeader(nsHttp::X_Firefox_TCP_Fast_Open,NS_LITERAL_CSTRING("data sent"));}elseif(mFastOpenStatus==TFO_TRIED){Unused<<mResponseHead->SetHeader(nsHttp::X_Firefox_TCP_Fast_Open,NS_LITERAL_CSTRING("tried negotiating"));}elseif(mFastOpenStatus==TFO_FAILED){Unused<<mResponseHead->SetHeader(nsHttp::X_Firefox_TCP_Fast_Open,NS_LITERAL_CSTRING("failed"));}// no header on TFO_NOT_TRIED caseif(LOG3_ENABLED()){LOG3(("http response [\n"));nsAutoCStringheaders;mResponseHead->Flatten(headers,false);headers.AppendLiteral(" OriginalHeaders");headers.AppendLiteral("\r\n");mResponseHead->FlattenNetworkOriginalHeaders(headers);LogHeaders(headers.get());LOG3(("]\n"));}CheckForStickyAuthScheme();// Save http version, mResponseHead isn't available anymore after// TakeResponseHead() is calledmHttpVersion=mResponseHead->Version();mHttpResponseCode=mResponseHead->Status();// notify the connection, give it a chance to cause a reset.boolreset=false;nsresultrv=mConnection->OnHeadersAvailable(this,mRequestHead,mResponseHead,&reset);NS_ENSURE_SUCCESS(rv,rv);// looks like we should ignore this response, resetting...if(reset){LOG(("resetting transaction's response head\n"));mHaveAllHeaders=false;mHaveStatusLine=false;mReceivedData=false;mSentData=false;mHttpResponseMatched=false;mResponseHead->Reset();// wait to be called again...returnNS_OK;}// check if this is a no-content responseswitch(mResponseHead->Status()){case101:mPreserveStream=true;MOZ_FALLTHROUGH;// to other no content cases:case204:case205:case304:mNoContent=true;LOG(("this response should not contain a body.\n"));break;case421:LOG(("Misdirected Request.\n"));gHttpHandler->ConnMgr()->ClearHostMapping(mConnInfo);// retry on a new connection - just in caseif(!mRestartCount){mCaps&=~NS_HTTP_ALLOW_KEEPALIVE;mForceRestart=true;// force restart has built in loop protectionreturnNS_ERROR_NET_RESET;}break;}if(mResponseHead->Status()==200&&mConnection->IsProxyConnectInProgress()){// successful CONNECTs do not have response bodiesmNoContent=true;}mConnection->SetLastTransactionExpectedNoContent(mNoContent);if(mNoContent){mContentLength=0;}else{// grab the content-length from the response headersmContentLength=mResponseHead->ContentLength();// handle chunked encoding here, so we'll know immediately when// we're done with the socket. please note that _all_ other// decoding is done when the channel receives the content data// so as not to block the socket transport thread too much.if(mResponseHead->Version()>=NS_HTTP_VERSION_1_0&&mResponseHead->HasHeaderValue(nsHttp::Transfer_Encoding,"chunked")){// we only support the "chunked" transfer encoding right now.mChunkedDecoder=newnsHttpChunkedDecoder();LOG(("nsHttpTransaction %p chunked decoder created\n",this));// Ignore server specified Content-Length.if(mContentLength!=int64_t(-1)){LOG(("nsHttpTransaction %p chunked with C-L ignores C-L\n",this));mContentLength=-1;if(mConnection){mConnection->DontReuse();}}}elseif(mContentLength==int64_t(-1))LOG(("waiting for the server to close the connection.\n"));}}mDidContentStart=true;returnNS_OK;}// called on the socket threadnsresultnsHttpTransaction::HandleContent(char*buf,uint32_tcount,uint32_t*contentRead,uint32_t*contentRemaining){nsresultrv;LOG(("nsHttpTransaction::HandleContent [this=%p count=%u]\n",this,count));*contentRead=0;*contentRemaining=0;MOZ_ASSERT(mConnection);if(!mDidContentStart){rv=HandleContentStart();if(NS_FAILED(rv))returnrv;// Do not write content to the pipe if we haven't started streaming yetif(!mDidContentStart)returnNS_OK;}if(mChunkedDecoder){// give the buf over to the chunked decoder so it can reformat the// data and tell us how much is really there.rv=mChunkedDecoder->HandleChunkedContent(buf,count,contentRead,contentRemaining);if(NS_FAILED(rv))returnrv;}elseif(mContentLength>=int64_t(0)){// HTTP/1.0 servers have been known to send erroneous Content-Length// headers. So, unless the connection is persistent, we must make// allowances for a possibly invalid Content-Length header. Thus, if// NOT persistent, we simply accept everything in |buf|.if(mConnection->IsPersistent()||mPreserveStream||mHttpVersion>=NS_HTTP_VERSION_1_1){int64_tremaining=mContentLength-mContentRead;*contentRead=uint32_t(std::min<int64_t>(count,remaining));*contentRemaining=count-*contentRead;}else{*contentRead=count;// mContentLength might need to be increased...int64_tposition=mContentRead+int64_t(count);if(position>mContentLength){mContentLength=position;//mResponseHead->SetContentLength(mContentLength);}}}else{// when we are just waiting for the server to close the connection...// (no explicit content-length given)*contentRead=count;}if(*contentRead){// update count of content bytes read and report progress...mContentRead+=*contentRead;}LOG(("nsHttpTransaction::HandleContent [this=%p count=%u read=%u mContentRead=%"PRId64" mContentLength=%"PRId64"]\n",this,count,*contentRead,mContentRead,mContentLength));// check for end-of-fileif((mContentRead==mContentLength)||(mChunkedDecoder&&mChunkedDecoder->ReachedEOF())){// the transaction is done with a complete response.mTransactionDone=true;mResponseIsComplete=true;ReleaseBlockingTransaction();if(TimingEnabled()){SetResponseEnd(TimeStamp::Now());}// report the entire response has arrivedif(mActivityDistributor){rv=mActivityDistributor->ObserveActivity(mChannel,NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE,PR_Now(),static_cast<uint64_t>(mContentRead),EmptyCString());if(NS_FAILED(rv)){LOG3(("ObserveActivity failed (%08x)",static_cast<uint32_t>(rv)));}}}returnNS_OK;}nsresultnsHttpTransaction::ProcessData(char*buf,uint32_tcount,uint32_t*countRead){nsresultrv;LOG(("nsHttpTransaction::ProcessData [this=%p count=%u]\n",this,count));*countRead=0;// we may not have read all of the headers yet...if(!mHaveAllHeaders){uint32_tbytesConsumed=0;do{uint32_tlocalBytesConsumed=0;char*localBuf=buf+bytesConsumed;uint32_tlocalCount=count-bytesConsumed;rv=ParseHead(localBuf,localCount,&localBytesConsumed);if(NS_FAILED(rv)&&rv!=NS_ERROR_NET_INTERRUPT)returnrv;bytesConsumed+=localBytesConsumed;}while(rv==NS_ERROR_NET_INTERRUPT);mCurrentHttpResponseHeaderSize+=bytesConsumed;if(mCurrentHttpResponseHeaderSize>gHttpHandler->MaxHttpResponseHeaderSize()){LOG(("nsHttpTransaction %p The response header exceeds the limit.\n",this));returnNS_ERROR_FILE_TOO_BIG;}count-=bytesConsumed;// if buf has some content in it, shift bytes to top of buf.if(count&&bytesConsumed)memmove(buf,buf+bytesConsumed,count);// report the completed response headerif(mActivityDistributor&&mResponseHead&&mHaveAllHeaders&&!mReportedResponseHeader){mReportedResponseHeader=true;nsAutoCStringcompleteResponseHeaders;mResponseHead->Flatten(completeResponseHeaders,false);completeResponseHeaders.AppendLiteral("\r\n");rv=mActivityDistributor->ObserveActivity(mChannel,NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_HEADER,PR_Now(),0,completeResponseHeaders);if(NS_FAILED(rv)){LOG3(("ObserveActivity failed (%08x)",static_cast<uint32_t>(rv)));}}}// even though count may be 0, we still want to call HandleContent// so it can complete the transaction if this is a "no-content" response.if(mHaveAllHeaders){uint32_tcountRemaining=0;//// buf layout://// +--------------------------------------+----------------+-----+// | countRead | countRemaining | |// +--------------------------------------+----------------+-----+//// count : bytes read from the socket// countRead : bytes corresponding to this transaction// countRemaining : bytes corresponding to next transaction on conn//// NOTE:// count > countRead + countRemaining <==> chunked transfer encoding//rv=HandleContent(buf,count,countRead,&countRemaining);if(NS_FAILED(rv))returnrv;// we may have read more than our share, in which case we must give// the excess bytes back to the connectionif(mResponseIsComplete&&countRemaining){MOZ_ASSERT(mConnection);rv=mConnection->PushBack(buf+*countRead,countRemaining);NS_ENSURE_SUCCESS(rv,rv);}if(!mContentDecodingCheck&&mResponseHead){mContentDecoding=mResponseHead->HasHeader(nsHttp::Content_Encoding);mContentDecodingCheck=true;}}returnNS_OK;}voidnsHttpTransaction::SetRequestContext(nsIRequestContext*aRequestContext){LOG(("nsHttpTransaction %p SetRequestContext %p\n",this,aRequestContext));mRequestContext=aRequestContext;}// Called when the transaction marked for blocking is associated with a connection// (i.e. added to a new h1 conn, an idle http connection, etc..)// It is safe to call this multiple times with it only// having an effect once.voidnsHttpTransaction::DispatchedAsBlocking(){if(mDispatchedAsBlocking)return;LOG(("nsHttpTransaction %p dispatched as blocking\n",this));if(!mRequestContext)return;LOG(("nsHttpTransaction adding blocking transaction %p from ""request context %p\n",this,mRequestContext.get()));mRequestContext->AddBlockingTransaction();mDispatchedAsBlocking=true;}voidnsHttpTransaction::RemoveDispatchedAsBlocking(){if(!mRequestContext||!mDispatchedAsBlocking)return;uint32_tblockers=0;nsresultrv=mRequestContext->RemoveBlockingTransaction(&blockers);LOG(("nsHttpTransaction removing blocking transaction %p from ""request context %p. %d blockers remain.\n",this,mRequestContext.get(),blockers));if(NS_SUCCEEDED(rv)&&!blockers){LOG(("nsHttpTransaction %p triggering release of blocked channels "" with request context=%p\n",this,mRequestContext.get()));rv=gHttpHandler->ConnMgr()->ProcessPendingQ();if(NS_FAILED(rv)){LOG(("nsHttpTransaction::RemoveDispatchedAsBlocking\n"" failed to process pending queue\n"));}}mDispatchedAsBlocking=false;}voidnsHttpTransaction::ReleaseBlockingTransaction(){RemoveDispatchedAsBlocking();LOG(("nsHttpTransaction %p request context set to null ""in ReleaseBlockingTransaction() - was %p\n",this,mRequestContext.get()));mRequestContext=nullptr;}voidnsHttpTransaction::DisableSpdy(){mCaps|=NS_HTTP_DISALLOW_SPDY;if(mConnInfo){// This is our clone of the connection info, not the persistent one that// is owned by the connection manager, so we're safe to change this heremConnInfo->SetNoSpdy(true);}}voidnsHttpTransaction::CheckForStickyAuthScheme(){LOG(("nsHttpTransaction::CheckForStickyAuthScheme this=%p",this));MOZ_ASSERT(mHaveAllHeaders);MOZ_ASSERT(mResponseHead);MOZ_ASSERT(OnSocketThread(),"not on socket thread");CheckForStickyAuthSchemeAt(nsHttp::WWW_Authenticate);CheckForStickyAuthSchemeAt(nsHttp::Proxy_Authenticate);}voidnsHttpTransaction::CheckForStickyAuthSchemeAt(nsHttpAtomconst&header){if(mCaps&NS_HTTP_STICKY_CONNECTION){LOG((" already sticky"));return;}nsAutoCStringauth;if(NS_FAILED(mResponseHead->GetHeader(header,auth))){return;}Tokenizerp(auth);nsAutoCStringschema;while(p.ReadWord(schema)){ToLowerCase(schema);nsAutoCStringcontractid;contractid.Assign(NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX);contractid.Append(schema);// using a new instance because of thread safety of auth modules refcntnsCOMPtr<nsIHttpAuthenticator>authenticator(do_CreateInstance(contractid.get()));if(authenticator){uint32_tflags;nsresultrv=authenticator->GetAuthFlags(&flags);if(NS_SUCCEEDED(rv)&&(flags&nsIHttpAuthenticator::CONNECTION_BASED)){LOG((" connection made sticky, found %s auth shema",schema.get()));// This is enough to make this transaction keep it's current connection,// prevents the connection from being released back to the pool.mCaps|=NS_HTTP_STICKY_CONNECTION;break;}}// schemes are separated with LFs, nsHttpHeaderArray::MergeHeaderp.SkipUntil(Tokenizer::Token::NewLine());p.SkipWhites(Tokenizer::INCLUDE_NEW_LINE);}}constTimingStructnsHttpTransaction::Timings(){mozilla::MutexAutoLocklock(mLock);TimingStructtimings=mTimings;returntimings;}voidnsHttpTransaction::SetDomainLookupStart(mozilla::TimeStamptimeStamp,boolonlyIfNull){mozilla::MutexAutoLocklock(mLock);if(onlyIfNull&&!mTimings.domainLookupStart.IsNull()){return;// We only set the timestamp if it was previously null}mTimings.domainLookupStart=timeStamp;}voidnsHttpTransaction::SetDomainLookupEnd(mozilla::TimeStamptimeStamp,boolonlyIfNull){mozilla::MutexAutoLocklock(mLock);if(onlyIfNull&&!mTimings.domainLookupEnd.IsNull()){return;// We only set the timestamp if it was previously null}mTimings.domainLookupEnd=timeStamp;}voidnsHttpTransaction::SetConnectStart(mozilla::TimeStamptimeStamp,boolonlyIfNull){mozilla::MutexAutoLocklock(mLock);if(onlyIfNull&&!mTimings.connectStart.IsNull()){return;// We only set the timestamp if it was previously null}mTimings.connectStart=timeStamp;}voidnsHttpTransaction::SetConnectEnd(mozilla::TimeStamptimeStamp,boolonlyIfNull){mozilla::MutexAutoLocklock(mLock);if(onlyIfNull&&!mTimings.connectEnd.IsNull()){return;// We only set the timestamp if it was previously null}mTimings.connectEnd=timeStamp;}voidnsHttpTransaction::SetRequestStart(mozilla::TimeStamptimeStamp,boolonlyIfNull){mozilla::MutexAutoLocklock(mLock);if(onlyIfNull&&!mTimings.requestStart.IsNull()){return;// We only set the timestamp if it was previously null}mTimings.requestStart=timeStamp;}voidnsHttpTransaction::SetResponseStart(mozilla::TimeStamptimeStamp,boolonlyIfNull){mozilla::MutexAutoLocklock(mLock);if(onlyIfNull&&!mTimings.responseStart.IsNull()){return;// We only set the timestamp if it was previously null}mTimings.responseStart=timeStamp;}voidnsHttpTransaction::SetResponseEnd(mozilla::TimeStamptimeStamp,boolonlyIfNull){mozilla::MutexAutoLocklock(mLock);if(onlyIfNull&&!mTimings.responseEnd.IsNull()){return;// We only set the timestamp if it was previously null}mTimings.responseEnd=timeStamp;}mozilla::TimeStampnsHttpTransaction::GetDomainLookupStart(){mozilla::MutexAutoLocklock(mLock);returnmTimings.domainLookupStart;}mozilla::TimeStampnsHttpTransaction::GetDomainLookupEnd(){mozilla::MutexAutoLocklock(mLock);returnmTimings.domainLookupEnd;}mozilla::TimeStampnsHttpTransaction::GetConnectStart(){mozilla::MutexAutoLocklock(mLock);returnmTimings.connectStart;}mozilla::TimeStampnsHttpTransaction::GetConnectEnd(){mozilla::MutexAutoLocklock(mLock);returnmTimings.connectEnd;}mozilla::TimeStampnsHttpTransaction::GetRequestStart(){mozilla::MutexAutoLocklock(mLock);returnmTimings.requestStart;}mozilla::TimeStampnsHttpTransaction::GetResponseStart(){mozilla::MutexAutoLocklock(mLock);returnmTimings.responseStart;}mozilla::TimeStampnsHttpTransaction::GetResponseEnd(){mozilla::MutexAutoLocklock(mLock);returnmTimings.responseEnd;}//-----------------------------------------------------------------------------// nsHttpTransaction deletion event//-----------------------------------------------------------------------------classDeleteHttpTransaction:publicRunnable{public:explicitDeleteHttpTransaction(nsHttpTransaction*trans):Runnable("net::DeleteHttpTransaction"),mTrans(trans){}NS_IMETHODRun()override{deletemTrans;returnNS_OK;}private:nsHttpTransaction*mTrans;};voidnsHttpTransaction::DeleteSelfOnConsumerThread(){LOG(("nsHttpTransaction::DeleteSelfOnConsumerThread [this=%p]\n",this));boolval;if(!mConsumerTarget||(NS_SUCCEEDED(mConsumerTarget->IsOnCurrentThread(&val))&&val)){deletethis;}else{LOG(("proxying delete to consumer thread...\n"));nsCOMPtr<nsIRunnable>event=newDeleteHttpTransaction(this);if(NS_FAILED(mConsumerTarget->Dispatch(event,NS_DISPATCH_NORMAL)))NS_WARNING("failed to dispatch nsHttpDeleteTransaction event");}}boolnsHttpTransaction::TryToRunPacedRequest(){if(mSubmittedRatePacing)returnmPassedRatePacing;mSubmittedRatePacing=true;mSynchronousRatePaceRequest=true;Unused<<gHttpHandler->SubmitPacedRequest(this,getter_AddRefs(mTokenBucketCancel));mSynchronousRatePaceRequest=false;returnmPassedRatePacing;}voidnsHttpTransaction::OnTokenBucketAdmitted(){mPassedRatePacing=true;mTokenBucketCancel=nullptr;if(!mSynchronousRatePaceRequest){nsresultrv=gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo);if(NS_FAILED(rv)){LOG(("nsHttpTransaction::OnTokenBucketAdmitted\n"" failed to process pending queue\n"));}}}voidnsHttpTransaction::CancelPacing(nsresultreason){if(mTokenBucketCancel){mTokenBucketCancel->Cancel(reason);mTokenBucketCancel=nullptr;}}//-----------------------------------------------------------------------------// nsHttpTransaction::nsISupports//-----------------------------------------------------------------------------NS_IMPL_ADDREF(nsHttpTransaction)NS_IMETHODIMP_(MozExternalRefCountType)nsHttpTransaction::Release(){nsrefcntcount;NS_PRECONDITION(0!=mRefCnt,"dup release");count=--mRefCnt;NS_LOG_RELEASE(this,count,"nsHttpTransaction");if(0==count){mRefCnt=1;/* stablize */// it is essential that the transaction be destroyed on the consumer// thread (we could be holding the last reference to our consumer).DeleteSelfOnConsumerThread();return0;}returncount;}NS_IMPL_QUERY_INTERFACE(nsHttpTransaction,nsIInputStreamCallback,nsIOutputStreamCallback)//-----------------------------------------------------------------------------// nsHttpTransaction::nsIInputStreamCallback//-----------------------------------------------------------------------------// called on the socket threadNS_IMETHODIMPnsHttpTransaction::OnInputStreamReady(nsIAsyncInputStream*out){MOZ_ASSERT(OnSocketThread(),"not on socket thread");if(mConnection){mConnection->TransactionHasDataToWrite(this);nsresultrv=mConnection->ResumeSend();if(NS_FAILED(rv))NS_ERROR("ResumeSend failed");}returnNS_OK;}//-----------------------------------------------------------------------------// nsHttpTransaction::nsIOutputStreamCallback//-----------------------------------------------------------------------------// called on the socket threadNS_IMETHODIMPnsHttpTransaction::OnOutputStreamReady(nsIAsyncOutputStream*out){MOZ_ASSERT(OnSocketThread(),"not on socket thread");mWaitingOnPipeOut=false;if(mConnection){mConnection->TransactionHasDataToRecv(this);nsresultrv=mConnection->ResumeRecv();if(NS_FAILED(rv))NS_ERROR("ResumeRecv failed");}returnNS_OK;}voidnsHttpTransaction::GetNetworkAddresses(NetAddr&self,NetAddr&peer){MutexAutoLocklock(mLock);self=mSelfAddr;peer=mPeerAddr;}boolnsHttpTransaction::CanDo0RTT(){if(mRequestHead->IsSafeMethod()&&(!mConnection||!mConnection->IsProxyConnectInProgress())){returntrue;}returnfalse;}boolnsHttpTransaction::Do0RTT(){if(mRequestHead->IsSafeMethod()&&(!mConnection||!mConnection->IsProxyConnectInProgress())){m0RTTInProgress=true;}returnm0RTTInProgress;}nsresultnsHttpTransaction::Finish0RTT(boolaRestart,boolaAlpnChanged/* ignored */){LOG(("nsHttpTransaction::Finish0RTT %p %d %d\n",this,aRestart,aAlpnChanged));MOZ_ASSERT(m0RTTInProgress);m0RTTInProgress=false;if(!aRestart&&(mEarlyDataDisposition==EARLY_SENT)){// note that if this is invoked by a 3 param version of finish0rtt this// disposition might be revertedmEarlyDataDisposition=EARLY_ACCEPTED;}if(aRestart){// Reset request headers to be sent again.nsCOMPtr<nsISeekableStream>seekable=do_QueryInterface(mRequestStream);if(seekable){seekable->Seek(nsISeekableStream::NS_SEEK_SET,0);}else{returnNS_ERROR_FAILURE;}}elseif(!mConnected){// this is code that was skipped in ::ReadSegments while in 0RTTmConnected=true;mConnection->GetSecurityInfo(getter_AddRefs(mSecurityInfo));}returnNS_OK;}nsresultnsHttpTransaction::RestartOnFastOpenError(){// This will happen on connection error during Fast Open or if connect// during Fast Open takes too long. So we should not have received any// data!MOZ_ASSERT(!mReceivedData);MOZ_ASSERT(OnSocketThread(),"not on socket thread");LOG(("nsHttpTransaction::RestartOnFastOpenError - restarting transaction ""%p\n",this));// rewind streams in case we already wrote out the requestnsCOMPtr<nsISeekableStream>seekable=do_QueryInterface(mRequestStream);if(seekable)seekable->Seek(nsISeekableStream::NS_SEEK_SET,0);// clear old connection state...mSecurityInfo=nullptr;if(!mConnInfo->GetRoutedHost().IsEmpty()){MutexAutoLocklock(*nsHttp::GetLock());RefPtr<nsHttpConnectionInfo>ci;mConnInfo->CloneAsDirectRoute(getter_AddRefs(ci));mConnInfo=ci;}mEarlyDataDisposition=EARLY_NONE;m0RTTInProgress=false;mFastOpenStatus=TFO_FAILED;returnNS_OK;}voidnsHttpTransaction::SetFastOpenStatus(uint8_taStatus){LOG(("nsHttpTransaction::SetFastOpenStatus %d [this=%p]\n",aStatus,this));mFastOpenStatus=aStatus;}voidnsHttpTransaction::Refused0RTT(){LOG(("nsHttpTransaction::Refused0RTT %p\n",this));if(mEarlyDataDisposition==EARLY_ACCEPTED){mEarlyDataDisposition=EARLY_SENT;// undo accepted state}}}// namespace net}// namespace mozilla